package  hivMultiCompartment;

/**
* GridWorld
* Extend the Repast Object2DGrid class to implement
* a world of discrete cells with these movement rules:
* * An object can't move into an occupied cell
* * Bounded world (can't move off the edges)
* This provides these methods:
* a) moveObject 
*    check the "physics" and moves (or not) an object 
* b) addToRandomLocationInWorld -- a version
*    that works best for worlds with density < 0.8 or so.
* c) getOpenNeighborLocations ( int x, int y )
*    return an arraylist of points for open cells around x,y
*
* Note: This assumes the grid contains objects that implement ObjectsInGrid,  
* objects that implement set/get-X/Y and draw() methods.
*
* This implements only one constructor, given  x,y-size of the world
* and the Model using the world/
*
 */


import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;

import uchicago.src.sim.space.*;

public class GridWorld extends Object2DGrid {

	/** the Model that is using this world. */
	public Model theModel;
	public Organ2DSpace theOrgan;

	public GridWorld(int sizeX, int sizeY, Model aModel, Organ2DSpace aOrgan) {
		super(sizeX, sizeY);
		theModel = aModel;
		theOrgan = aOrgan;
	}

	/**
	 * placeAtRandomLocationInWorld
	 * 
	 * @param ObjectInGrid object to be placed in world
	 * @return boolean placed or not place the object in a randomly selected
	 *         empty location tell the object to set its x,y accordingly
	 * 
	 *         NB: if it can't find a spot after X*Y trials, it gives up and
	 *         returns (added=) false. Works best for densities < 0.8 or so.
	 */

	public boolean placeAtRandomLocation(ObjectInGrid obj) {
		int x = 0, y = 0; // candidate x,y locations
		int maxTrials = xSize * ySize; // only try this many times
		int numTrials = 0; // haven't tried to look at any cells yet
		Boolean foundOpenCell = false; // not found empty cell (so not added)

		// find a randomly selected empty location, or give up if takes too long
		while (!foundOpenCell && numTrials < maxTrials) {
			x = Model.getUniformIntFromTo(0, xSize - 1);
			y = Model.getUniformIntFromTo(0, ySize - 1);
			if ( getObjectAt(x, y) == null ) // found empty cell!
				foundOpenCell = true;
			else
				// still need to look
				++numTrials; //  increment number of times we tried
		}

		if ( foundOpenCell ) { // if true, we can:
			putObjectAt( x, y, obj ); // put obj in world at that location
			obj.setX( x ); // tell the object where we put it
			obj.setY( y );
		}

		return foundOpenCell;
	}
	
	//coordinate processing
	/**
	 * process the coordinate.
	 * can be overridden
	 * @param x
	 * @return
	 */
	public int processedX (int x) {
		return x;
	}
	
	public int processedY (int y) {
		return y;
	}
	

	


	/**
	 * moveObjectTo
	 * 
	 * @param ObjectInGrid
	 * @param int newX
	 * @param int newY
	 * @return boolean moved or not try to move object to the requested newX,newY .
	 * 		   process the destination coordinates first.
	 *         note we must check to be sure the new location is "legal": - cell
	 *         is not off the edge of the world - cell is empty If move is ok:
	 *         move in world, tell object its new x,y, and return true if not
	 *         ok, return false
	 */
	public boolean moveObjectTo(int newX, int newY,  ObjectInGrid obj) {
		
		int processedNewX = processedX(newX);
		int processedNewY = processedY(newY);
		
		// first check to be sure new location is in the world!
		if ( processedNewX < 0 || processedNewY < 0 || processedNewX >= xSize || processedNewY >= ySize ) {
			return false;
		}
		
		// its ok to move, so tell the world and the object about the move
		removeObjectFromGrid(obj);
		addObjectAt(processedNewX, processedNewY, obj);
		return true;
	}
	
	/**
	 * moveObject
	 * 
	 * @param ObjectInGrid
	 * @param int dX
	 * @param int dY
	 * @return boolean moved or not try to move object to the requested dx,dy .
	 *         note we must check to be sure the new location is "legal": - cell
	 *         is not off the edge of the world - cell is empty If move is ok:
	 *         move in world, tell object its new x,y, and return true if not
	 *         ok, return false
	 */
	public boolean moveObject(int dX, int dY, ObjectInGrid obj) {
		int newX = obj.getX() + dX; // get location it wants to move to
		int newY = obj.getY() + dY;
		boolean result;
		if ( getObjectAt(newX, newY) != null ) {
			result =  false;
		}
		else {
			result = moveObjectTo(newX, newY, obj);
		}
		return result;
	}
	
	/**
	 * 
	 * @param x current x
	 * @param y current y
	 * @param r range
	 * @return ArrayList<Point> returns a ArrayList of Points, one point for
	 *         each cell in the Moore neighborhood around the given
	 *         x,y location. NB: (x,y) is excluded.
	 */
	public ArrayList<Point> getNeighborLocations(int x, int y, int r) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		
		int range = r;
		int pX, pY;
		// figure out the range of cells to search
		int minX = x - range;
		int maxX = x + range;
		int minY = y - range;
		int maxY = y + range;

		// search area for open cells
		for (int ty = minY; ty <= maxY; ++ty) {
			for (int tx = minX; tx <= maxX; ++tx) {
				pX = processedX(tx);
				pY = processedY(ty);
				if(pX >= xSize || pX < 0 || pY >= ySize || pY < 0 || (pX == x && pY == y)) {
					continue;
				}
				Point p = new Point(pX, pY);
				ptList.add(p);
			}
		}		
		return ptList;
		
	}
	/**
	 * return a list of points designating the open neighbor cells within distance of r
	 * original position is excluded.
	 * @param x
	 * @param y
	 * @param r
	 * @return
	 */
	public ArrayList<Point> getOpenNeighborLocations(int x, int y, int r) {
		ArrayList<Point> ptList = new ArrayList<Point>();
		//System.out.printf("All neighbors: %d\n", ptList.size());
		
		int range = r;
		int pX, pY;
		// figure out the range of cells to search
		int minX = x - range;
		int maxX = x + range;
		int minY = y - range;
		int maxY = y + range;

		// search area for open cells
		for (int ty = minY; ty <= maxY; ++ty) {
			for (int tx = minX; tx <= maxX; ++tx) {
				pX = processedX(tx);
				pY = processedY(ty);
				if(pX >= xSize || pX < 0 || pY >= ySize || pY < 0 || (pX == x && pY == y)) {
					continue;
				}
				if (getObjectAt(pX, pY) == null) {
					Point p = new Point(pX, pY);
					ptList.add(p);
				}
			}
		}		
		return ptList;
	}
	
	/**
	 * return a random point from the open neighbors
	 * @param x
	 * @param y
	 * @param r
	 * @return
	 */
	public Point getRandomOpenNeighborLocation(int x, int y, int r) {
		ArrayList<Point> ptList = getOpenNeighborLocations(x, y, r);
		int nrPt = ptList.size();
		if (nrPt > 0) {
			Point openP = ptList.get( Model.getUniformIntFromTo( 0, nrPt-1 ) );
			return openP;
		}
		else {
			return null;
		}
	}

	/**
	 * This functions takes in the coordinate and returns an arrayList of Moore neighbors with specified type
	 * @param x my X coordinate
	 * @param y my Y coordinate
	 * @param r range
	 * @param type Wanted Neighbor Type
	 * @return
	 */
	public ArrayList<Object> getMooreNeighborOfType (int x, int y, int r, Class type) {
		ArrayList<Object> neighborsOfWantedType = new ArrayList<Object>();
		
		int range = r;
		int pX, pY;
		// figure out the range of cells to search
		int minX = x - range;
		int maxX = x + range;
		int minY = y - range;
		int maxY = y + range;

		// search area for given type
		for (int ty = minY; ty <= maxY; ++ty) {
			for (int tx = minX; tx <= maxX; ++tx) {
				pX = processedX(tx);
				pY = processedY(ty);
				if(pX >= xSize || pX < 0 || pY >= ySize || pY < 0 || (pX == x && pY == y)) {
					continue;
				}
				Object obj = getObjectAt(pX, pY);
				if ( obj != null) {
					if ((type.isInstance(obj))){
						neighborsOfWantedType.add(obj);
					}
				}
			}
		}		
		return neighborsOfWantedType;
	}
	
	/**
	 * 
	 * @param x
	 * @param y
	 * @param o
	 * @return
	 */
	
	public boolean addObjectAt (int x, int y, ObjectInGrid o) {
		boolean result = false;
		x = processedX(x);
		y = processedY(y);
		o.setX(x);
		o.setY(y);
		if (getObjectAt(x, y) == null) {
			putObjectAt(x, y, o);
			result = true;
		}
		return result;
	}
	
	/**
	 * remove ObjectInGrid o from grid
	 * @param o
	 */
	public void removeObjectFromGrid (ObjectInGrid o) {
		putObjectAt(o.getX(), o.getY(), null);
	}
}
